/*
 * @Author: soso
 * @Date: 2022-01-05 18:55:23
 * @LastEditTime: 2022-03-15 11:23:14
 * @LastEditors: Please set LastEditors
 * @Description: 通用工具
 * @FilePath: /file-sync/utils/utils.go
 */
package utils

import (
	"bytes"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"io/fs"
	"os"
	"reflect"
	"runtime"
)

func FileExist(filePath string) (fs.FileInfo, error) {
	fileInfo, err := os.Stat(filePath)
	return fileInfo, err
}

// 获取文件的md5码
func HashFileMd5(filename string) (md5str string, err error) {
	pFile, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer pFile.Close()

	md5h := md5.New()
	io.Copy(md5h, pFile)
	return hex.EncodeToString(md5h.Sum(nil)), err
}

// 获取大文件的Md5值
func HashLargeFileMD5(path string) string {
	file, err := os.Open(path)
	if err != nil {
		return ""
	}
	defer file.Close()

	hash, buf := md5.New(), make([]byte, 1<<20)
	for {
		n, err := file.Read(buf)
		if n == 0 {
			if err == nil {
				continue
			}
			if err == io.EOF {
				break
			}
			return ""
		}

		io.Copy(hash, bytes.NewReader(buf[:n]))
	}

	return hex.EncodeToString(hash.Sum(nil))
}

func StringMd5(val []byte) []byte {
	_md5 := md5.New()
	_md5.Write(val)
	return _md5.Sum(nil)
}

func getFrame(skipFrames int) runtime.Frame {
	// We need the frame at index skipFrames+2, since we never want runtime.Callers and getFrame
	targetFrameIndex := skipFrames + 2

	// Set size to targetFrameIndex+2 to ensure we have room for one more caller than we need
	programCounters := make([]uintptr, targetFrameIndex+2)
	n := runtime.Callers(0, programCounters)

	frame := runtime.Frame{Function: "unknown"}
	if n > 0 {
		frames := runtime.CallersFrames(programCounters[:n])
		for more, frameIndex := true, 0; more && frameIndex <= targetFrameIndex; frameIndex++ {
			var frameCandidate runtime.Frame
			frameCandidate, more = frames.Next()
			if frameIndex == targetFrameIndex {
				frame = frameCandidate
			}
		}
	}

	return frame
}

// MyCaller returns the caller of the function that called it :)
func MyCaller() string {
	// Skip GetCallerFunctionName and the function to get the caller of
	return getFrame(2).Function
}

// ToMap 结构体转为Map[string]interface{}
func ToMap(in interface{}, tagName string) (map[string]interface{}, error) {
	out := make(map[string]interface{})

	v := reflect.ValueOf(in)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}

	if v.Kind() != reflect.Struct { // 非结构体返回错误提示
		return nil, fmt.Errorf("ToMap only accepts struct or struct pointer; got %T", v)
	}

	t := v.Type()
	// 遍历结构体字段
	// 指定tagName值为map中key;字段值为map中value
	for i := 0; i < v.NumField(); i++ {
		fi := t.Field(i)
		if tagValue := fi.Tag.Get(tagName); tagValue != "" {
			out[tagValue] = v.Field(i).Interface()
		}
	}
	return out, nil
}
